/*********************************************************
@file

@author J. Di Mattina

@brief Serialization functions for c-realize library.

This file has the implementation of the serialize 
functionality for the c-realize library.
*********************************************************/
/* standard library includes */
#include <stdlib.h>
#include <stdint.h>
#include <stdarg.h>

/* local includes */
#include "Crz.h"
#include "Crz_Utils.h"

/*********************************************************
 Static Functions
 *********************************************************/
/*********************************************************
 calcBufSize
*//**
@brief Calculate the size required for the buffer.

Calculates the size requirement for the data buffer based
on the format string. In the case of arrays it is necessary
to read in the uint32_t size variable to get the number of
elements.

@param [in]  fmt    Format string descibing the vararg
@param [in]  ...    Vararg of data elements

@return Size requirement for the data buffer in bytes.
**********************************************************/
static uint32_t
calcBufSize(char *fmt, va_list ap)
{
    uint32_t size;
    Crz_Arg arg;
    uint32_t arrayBase = 0;
    uint32_t arraySize = 0;

    while (*fmt)
    {
        switch(*fmt)
        {
        case '1':
            arg.u8 = (uint8_t) va_arg(ap, uint32_t);
            size++;
            break;
        case '2':
            arg.u16 = (uint16_t) va_arg(ap, uint32_t);
            size += 2;
            break;
        case '4':
            arg.u32 = va_arg(ap, uint32_t);
            size += 4;
            break;
        case '8':
            arg.u64 = va_arg(ap, uint64_t);
            size += 8;
            break;
        case '*':
            /* first get the array ptr */
            arg.ptr = va_arg(ap, void *);
            
            /* get the size of the array item */
            arraySize = va_arg(ap, uint32_t);
            switch (*(++fmt))
            {
            case '1':
                arrayBase = 1;
                break;
            case '2':
                arrayBase = 2;
                break;
            case '4':
                arrayBase = 4;
                break;
            case '8':
                arrayBase = 8;
                break;
            }

            /* compute the size of the array */
            arraySize *= arrayBase;

            /* add the array item size to total size */
            size += arraySize;
            break;
        case '{':
            /** @todo add something */
            break;
        default:
            break;
        }
        fmt++;
    }
    return size;
}

/*********************************************************
 Public Functions
 *********************************************************/

/*********************************************************
 Crz_Serialize
*//**
@brief Create a serialized data buffer.

The c-realize library creates a data format string which
is used as input to this function for packing messages
described in the .cr files.

The memory allocation for the buffer is handled by the
library, so a NULL buffer should be passed in.

The end result is buf being filled with a serialized version
of the data structure described in the .h file.

@param [out] buf    Buffer to store serialized data
@param [in]  fmt    Format string descibing the vararg
@param [in]  ...    Vararg of items to pack

@return CRZ_SUCCESS on success,
        corrosponding error code on failure.
**********************************************************/
Crz_Result
Crz_Serialize(char **buf, char *fmt, ...)
{
    Crz_Result result = CRZ_SUCCESS;
    Crz_Arg arg;
    char *packed;
    va_list ap;
    va_list apSize;
    uint32_t bufSize = 0;
    uint32_t arrayElements = 0;
    uint32_t index = 0;

    /* check parameters */
    if (buf == NULL || fmt == NULL)
    {
        return CRZ_ERROR_INVALID_PARAM;
    }

    /* start the arg list */
    va_start(apSize, fmt);

    /* precalculate the buffer size and then allocate */
    bufSize = calcBufSize(fmt, apSize);
    va_end(apSize);

    *buf = malloc(bufSize);
    if (!*buf)
    {
        return CRZ_ERROR_MEM_ALLOC;
    }    

    /* start the new array */
    va_start(ap, fmt);

    /* packed buffer */
    packed = *buf;

    while (*fmt)
    {
        switch(*fmt)
        {
        case '1':
            arg.u8 = (uint8_t) va_arg(ap, uint32_t);
            /* already in network byte order, just pack */
            *(packed)++ = arg.u8;
            break;
        case '2':
            arg.u16 = (uint16_t) va_arg(ap, uint32_t);
            *(uint16_t *)packed = Crz_hton16(arg.u16);
            packed += 2;
            break;
        case '4':
            arg.u32 = va_arg(ap, uint32_t);
            *(uint32_t *)packed = Crz_hton32(arg.u32);
            packed += 4;
            break;
        case '8':
            arg.u64 = va_arg(ap, uint64_t);
            *(uint64_t *)packed = Crz_hton64(arg.u64);
            packed += 8;
            break;
        case '*':
            /* read the array pointer */
            arg.ptr = (void *) va_arg(ap, void *);

            /* get the size of the array item */
            arrayElements = va_arg(ap, uint32_t);

            /* now add according to element size */
            switch (*(++fmt))
            {
            case '1':
                {
                    /* set temp ptr */
                    uint8_t *u8Ptr = arg.ptr;
                    
                    for (index = 0; index < arrayElements; index++)
                    {
                        *packed++ = *u8Ptr++;
                    }                    
                }
                break;
            case '2':
                {
                    /* set temp ptr */
                    uint16_t *u16Ptr = arg.ptr;
                    
                    for (index = 0; index < arrayElements; index++)
                    {
                        *(uint16_t *) packed = Crz_hton16(*u16Ptr++);
                        packed += 2;
                    }                    
                }
                break;
            case '4':
                {
                    /* set temp ptr */
                    uint32_t *u32Ptr = arg.ptr;
                    
                    for (index = 0; index < arrayElements; index++)
                    {
                        *(uint32_t *) packed = Crz_hton32(*u32Ptr++);
                        packed += 4;
                    }
                }
                break;
            case '8':
                {
                    /* set temp ptr */
                    uint64_t *u64Ptr = arg.ptr;
                    
                    for (index = 0; index < arrayElements; index++)
                    {
                        *(uint64_t *) packed = Crz_hton64(*u64Ptr++);
                        packed += 8;
                    }
                }
                break;
            case '{':
                /** @todo Add struct array support */
            default:
                /** @todo Add error message */
                break;
            }
            break;
        case '{':
            /** @todo Add struct support */
            break;
        default:
            /** @todo Add error message */
            break;
        }
        fmt++;
    }

    /* close the array */
    va_end(ap);

    return result;
}
